package dkbta.ontology;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import dkbta.ontology.elements.ElementDef;
import dkbta.ontology.elements.ElementType;
import dkbta.ontology.elements.abstractions.gradient.GradientDef;
import dkbta.ontology.elements.abstractions.rate.RateDef;
import dkbta.ontology.elements.abstractions.state.StateDef;
import dkbta.ontology.elements.abstractions.trend.TrendDef;
import dkbta.ontology.elements.context.ContextDef;
import dkbta.ontology.elements.event.EventDef;
import dkbta.ontology.elements.pattern.PatternDef;
import dkbta.ontology.elements.primitive.PrimitiveDef;
import dkbta.ontology.elements.repeatingpattern.RepeatingPatternDef;
import dkbta.ontology.elements.stat.StatDef;
import dkbta.ontology.elements.subjectstat.SubjectStatDef;
import dkbta.ontology.loading.OntologyException;
import dkbta.util.Log;

/**
 * The K in KBTA :)
 * 
 * @author Uri Kanonov
 */
public class Ontology{
	private final Map<String, PrimitiveDef> _primitives;

	private final Map<String, EventDef> _events;

	private final Map<String, ContextDef> _contexts;

	private final Map<String, StateDef> _states;

	private final Map<String, GradientDef> _gradients;

	private final Map<String, RateDef> _rates;

	private final Map<String, TrendDef> _trends;

	private final Map<String, PatternDef> _patterns;

	private final Map<String, RepeatingPatternDef> _repeatingPatterns;

	private final Map<String, SubjectStatDef> _subjectStats;

	private final Map<String, StatDef> _stats;

	private final String _name;

	/**
	 * Initializes an ontology with the given name
	 * 
	 * @param name The ontology's name
	 */
	public Ontology(String name){
		_name = name;
		_primitives = new HashMap<String, PrimitiveDef>();
		_events = new HashMap<String, EventDef>();
		_contexts = new HashMap<String, ContextDef>();
		_states = new HashMap<String, StateDef>();
		_gradients = new HashMap<String, GradientDef>();
		_rates = new HashMap<String, RateDef>();
		_trends = new HashMap<String, TrendDef>();
		_patterns = new HashMap<String, PatternDef>();
		_repeatingPatterns = new HashMap<String, RepeatingPatternDef>();
		_subjectStats = new HashMap<String, SubjectStatDef>();
		_stats = new HashMap<String, StatDef>();
	}

	@SuppressWarnings("unchecked")
	public void validateOntology() throws OntologyException{
		validateOntology(_primitives, _events, _contexts, _states, _gradients, _rates,
			_trends, _patterns, _repeatingPatterns, _subjectStats, _stats);
	}

	/**
	 * Performs a full recursive validation of all the elements in the ontology
	 * 
	 * @param elementsCollections The element definitions to be validated
	 * @throws OntologyException If the ontology wasn't valid
	 */
	private void validateOntology(
		Map<String, ? extends ElementDef>... elementsCollections)
			throws OntologyException{
		Log logger = Log.getLogger();
		boolean hadInvalidElements = false;
		boolean allValid = false;
		while (!allValid){
			allValid = true;
			for (Map<String, ? extends ElementDef> elements : elementsCollections){
				Iterator<? extends ElementDef> iter = elements.values().iterator();
				while (iter.hasNext()){
					ElementDef e = iter.next();
					try{
						e.validate(this);
					}catch(OntologyException oe){
						hadInvalidElements = true;
						allValid = false;
						logger.warning("Invalid " + e.getElementType() + ": "
								+ e.getName(), oe);
						iter.remove(); // Removing the invalid element
					}
				}
			}
		}
		if (hadInvalidElements){
			throw new OntologyException(
					"Ontology is not valid, please look at the log/stderr and correct it");
		}
	}

	@SuppressWarnings("unused")
	private void print(Map<String, ? extends ElementDef>... elementsCollections){
		for (Map<String, ? extends ElementDef> elements : elementsCollections){
			for (ElementDef e : elements.values()){
				System.out.println(e.completeToString());
			}
		}
	}

	/**
	 * Adds a primitive to the ontology
	 * 
	 * @param primitive The primitive
	 * @throws OntologyException If the element already exists in the ontology
	 */
	public void addPrimitive(PrimitiveDef primitive) throws OntologyException{
		add(primitive, _primitives);
	}

	/**
	 * Retrieves the primitive with the given name
	 * 
	 * @param name The name
	 * @return The requested primitive or null if no primitive with the required name
	 *         exists in the ontology
	 */
	public PrimitiveDef getPrimitive(String name){
		return _primitives.get(name);
	}

	/**
	 * Retrieves all of the primitives that are defined in the ontology
	 * 
	 * @return All of the primitives that are defined in the ontology
	 */
	public Collection<PrimitiveDef> getAllPrimitives(){
		return _primitives.values();
	}

	/**
	 * Adds an event to the ontology
	 * 
	 * @param event The event
	 * @throws OntologyException If the element already exists in the ontology
	 */
	public void addEvent(EventDef event) throws OntologyException{
		add(event, _events);
	}

	/**
	 * Retrieves the event with the given name
	 * 
	 * @param name The name
	 * @return The requested event or null if no event with the required name exists in
	 *         the ontology
	 */
	public EventDef getEvent(String name){
		return _events.get(name);
	}

	/**
	 * Retrieves all of the primitives that are defined in the ontology
	 * 
	 * @return All of the primitives that are defined in the ontology
	 */
	public Collection<EventDef> getAllEvents(){
		return _events.values();
	}

	/**
	 * Adds a context to the ontology
	 * 
	 * @param context The context
	 * @throws OntologyException If the element already exists in the ontology
	 */
	public void addContext(ContextDef context) throws OntologyException{
		add(context, _contexts);
	}

	/**
	 * Retrieves the context with the given name
	 * 
	 * @param name The name
	 * @return The requested context or null if no context with the required name exists
	 *         in the ontology
	 */
	public ContextDef getContext(String name){
		return _contexts.get(name);
	}

	/**
	 * Retrieves all of the contexts that are defined in the ontology
	 * 
	 * @return All of the contexts that are defined in the ontology
	 */
	public Collection<ContextDef> getAllContexts(){
		return _contexts.values();
	}

	/**
	 * Adds a state to the ontology
	 * 
	 * @param state The state
	 * @throws OntologyException If the element already exists in the ontology
	 */
	public void addState(StateDef state) throws OntologyException{
		add(state, _states);
	}

	/**
	 * Retrieves the state with the given name
	 * 
	 * @param name The name
	 * @return The requested state or null if no state with the required name exists in
	 *         the ontology
	 */
	public StateDef getState(String name){
		return _states.get(name);
	}

	/**
	 * Retrieves all of the states that are defined in the ontology
	 * 
	 * @return All of the states that are defined in the ontology
	 */
	public Collection<StateDef> getAllStates(){
		return _states.values();
	}

	/**
	 * Adds a gradient to the ontology
	 * 
	 * @param gradient The gradient
	 * @throws OntologyException If the element already exists in the ontology
	 */
	public void addGradient(GradientDef gradient) throws OntologyException{
		add(gradient, _gradients);
	}

	/**
	 * Retrieves the gradient with the given name
	 * 
	 * @param name The name
	 * @return The requested gradient or null if no gradient with the required name exists
	 *         in the ontology
	 */
	public GradientDef getGradient(String name){
		return _gradients.get(name);
	}

	/**
	 * Retrieves all of the gradients that are defined in the ontology
	 * 
	 * @return All of the gradients that are defined in the ontology
	 */
	public Collection<GradientDef> getAllGradients(){
		return _gradients.values();
	}

	/**
	 * Adds a rate to the ontology
	 * 
	 * @param rate The rate
	 * @throws OntologyException If the element already exists in the ontology
	 */
	public void addRate(RateDef rate) throws OntologyException{
		add(rate, _rates);
	}

	/**
	 * Retrieves the rate with the given name
	 * 
	 * @param name The name
	 * @return The requested rate or null if no rate with the required name exists in the
	 *         ontology
	 */
	public RateDef getRate(String name){
		return _rates.get(name);
	}

	/**
	 * Retrieves all of the rates that are defined in the ontology
	 * 
	 * @return All of the rates that are defined in the ontology
	 */
	public Collection<RateDef> getAllRates(){
		return _rates.values();
	}

	/**
	 * Adds a trend to the ontology
	 * 
	 * @param trend The trend
	 * @throws OntologyException If the element already exists in the ontology
	 */
	public void addTrend(TrendDef trend) throws OntologyException{
		add(trend, _trends);
	}

	/**
	 * Retrieves the trend with the given name
	 * 
	 * @param name The name
	 * @return The requested trend or null if no rate with the required name exists in the
	 *         ontology
	 */
	public TrendDef getTrend(String name){
		return _trends.get(name);
	}

	/**
	 * Retrieves all of the trends that are defined in the ontology
	 * 
	 * @return All of the trends that are defined in the ontology
	 */
	public Collection<TrendDef> getAllTrends(){
		return _trends.values();
	}

	/**
	 * Adds a pattern to the ontology
	 * 
	 * @param pattern The pattern
	 * @throws OntologyException If the element already exists in the ontology
	 */
	public void addPattern(PatternDef pattern) throws OntologyException{
		add(pattern, _patterns);
	}

	/**
	 * Retrieves the pattern with the given name
	 * 
	 * @param name The name
	 * @return The requested pattern or null if no pattern with the required name exists
	 *         in the ontology
	 */
	public PatternDef getPattern(String name){
		return _patterns.get(name);
	}

	/**
	 * Retrieves all of the patterns that are defined in the ontology
	 * 
	 * @return All of the patterns that are defined in the ontology
	 */
	public Collection<PatternDef> getAllPatterns(){
		return _patterns.values();
	}

	/**
	 * Adds a repeating pattern to the ontology
	 * 
	 * @param repeatingPattern The repeating pattern
	 * @throws OntologyException If the element already exists in the ontology
	 */
	public void addRepeatingPattern(RepeatingPatternDef repeatingPattern)
			throws OntologyException{
		add(repeatingPattern, _repeatingPatterns);
	}

	/**
	 * Retrieves the repeating pattern with the given name
	 * 
	 * @param name The name
	 * @return The requested repeating pattern or null if no repeating pattern with the
	 *         required name exists in the ontology
	 */
	public RepeatingPatternDef getRepeatingPattern(String name){
		return _repeatingPatterns.get(name);
	}

	/**
	 * Retrieves all of the repeating patterns that are defined in the ontology
	 * 
	 * @return All of the repeating patterns that are defined in the ontology
	 */
	public Collection<RepeatingPatternDef> getAllRepeatingPatterns(){
		return _repeatingPatterns.values();
	}

	/**
	 * Adds a subject-statistical abstraction to the ontology
	 * 
	 * @param pattern The subject-statistical abstraction
	 * @throws OntologyException If the element already exists in the ontology
	 */
	public void addSubjectStat(SubjectStatDef subjectStat) throws OntologyException{
		add(subjectStat, _subjectStats);
	}

	/**
	 * Retrieves the subject-statistical abstraction with the given name
	 * 
	 * @param name The name
	 * @return The requested subject-statistical abstraction or null if no pattern with the
	 *         required name exists in the ontology
	 */
	public SubjectStatDef getSubjectStat(String name){
		return _subjectStats.get(name);
	}

	/**
	 * Retrieves all of the subject-statistical abstractions that are defined in the ontology
	 * 
	 * @return All of the subject-statistical abstractions that are defined in the ontology
	 */
	public Collection<SubjectStatDef> getAllSubjectStats(){
		return _subjectStats.values();
	}
	
	/**
	 * Adds a statistical abstraction to the ontology
	 * 
	 * @param pattern The statistical abstraction
	 * @throws OntologyException If the element already exists in the ontology
	 */
	public void addStat(StatDef stat) throws OntologyException{
		add(stat, _stats);
	}

	/**
	 * Retrieves the statistical abstraction with the given name
	 * 
	 * @param name The name
	 * @return The requested statistical abstraction or null if no pattern with the
	 *         required name exists in the ontology
	 */
	public StatDef getStat(String name){
		return _stats.get(name);
	}

	/**
	 * Retrieves all of the statistical abstractions that are defined in the ontology
	 * 
	 * @return All of the statistical abstractions that are defined in the ontology
	 */
	public Collection<StatDef> getAllStats(){
		return _stats.values();
	}

	/**
	 * Retrieves an element from the ontology according to it's name and type
	 * 
	 * @param eType The element's type
	 * @param name The element's name
	 * @return The element or null if the name is null, eType is null or the element isn't
	 *         in the ontology
	 */
	public ElementDef getElement(ElementType eType, String name){
		if (eType == null || name == null){
			throw new NullPointerException("Element type and name cannot be null");
		}else{
			switch (eType){
				case PRIMITIVE:
					return getPrimitive(name);
				case EVENT:
					return getEvent(name);
				case CONTEXT:
					return getContext(name);
				case STATE:
					return getState(name);
				case GRADIENT:
					return getGradient(name);
				case RATE:
					return getRate(name);
				case TREND:
					return getTrend(name);
				case PATTERN:
					return getPattern(name);
				case REPEATINGPATTERN:
					return getRepeatingPattern(name);
				case SUBJECTSTAT:
					return getSubjectStat(name);
				case STAT:
					return getStat(name);
				default:
					throw new UnsupportedOperationException("Getter not implemented for "
							+ eType);
			}
		}
	}

	/**
	 * Retrieves all of the of the elements for the element type that are defined in the
	 * ontology
	 * 
	 * @param eType The element's type
	 * @return All of the elements for the element type that are defined in the ontology
	 */
	public Collection<? extends ElementDef> getAllElements(ElementType eType){
		if (eType == null){
			throw new NullPointerException("Element type cannot be null");
		}else{
			switch (eType){
				case PRIMITIVE:
					return getAllPrimitives();
				case EVENT:
					return getAllEvents();
				case CONTEXT:
					return getAllContexts();
				case STATE:
					return getAllStates();
				case GRADIENT:
					return getAllGradients();
				case RATE:
					return getAllRates();
				case TREND:
					return getAllTrends();
				case PATTERN:
					return getAllPatterns();
				case REPEATINGPATTERN:
					return getAllRepeatingPatterns();
				case SUBJECTSTAT:
					return getAllSubjectStats();
				case STAT:
					return getAllStats();
				default:
					throw new UnsupportedOperationException("Getter not implemented for "
							+ eType);
			}
		}
	}

	/**
	 * Adds an element to the ontology
	 * 
	 * @param element The element
	 * @throws OntologyException
	 */
	private <T extends ElementDef> void add(T element, Map<String, T> elements)
			throws OntologyException{
		String name = element.getName();
		if (elements.containsKey(name)){
			throw new OntologyException("Ontology already contains a "
					+ element.getElementType() + " by the name of: " + name);
		}else{
			elements.put(name, element);
		}
	}

	/**
	 * Returns the name of the ontology
	 * 
	 * @return The name of the ontology
	 */
	public String getName(){
		return _name;
	}

	@Override
	public String toString(){
		return getName();
	}
}
